package com.example.shop.redis.service.impl;

import com.example.shop.redis.service.LettuceClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
public class LettuceClientImpl implements LettuceClient {


    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

//===============================通用=============================================


    /**
     * lua脚本的执行
     * @param script lua脚本
     * @param keys KEYS的键名
     * @param args ARGV的值
     * @param <T> 返回值类型
     * @return 返回脚本的执行结果
     */
    @Override
    public <T>  T execLua(RedisScript<T> script, List<String> keys, Object... args){
        return redisTemplate.execute(script,keys,args);
    }

    /**
     * lua脚本的执行
     * @param script lua脚本
     * @param resultSerializer 返回结果的双向序列化器
     * @param keys KEYS的键名
     * @param args ARGV的值
     * @param <T> 返回值类型
     * @return 返回脚本的执行结果
     */
    @Override
    public <T>  T execLua(RedisScript<T> script, RedisSerializer<T> resultSerializer,
                          List<String> keys, Object... args){
        return redisTemplate.execute(script, redisTemplate.getValueSerializer(),resultSerializer,keys,args);
    }

    /**
     * 判断指定的key是否存在
     * @param key 要知道存在的键名，任意类型
     * @return 返回是否存在，存在为true
     */
    @Override
    public boolean existKey(String key){
        return redisTemplate.hasKey(key);
    }

    /**
     * 统计keys有多少个key是存在的
     * @param keys 要统计的键名，任意类型
     * @return 返回存在key的个数
     */
    @Override
    public long countKey(List<String> keys){
        return redisTemplate.countExistingKeys(keys);
    }

    /**
     * 移除给定 key的生存时间
     * @param key 要移除过期时间的键名，任意类型
     * @return 当生存时间移除成功时，返回1,如果 key 不存在或 key 没有设置生存时间,返回 0。
     */
    @Override
    public boolean persist(String key){
        return redisTemplate.persist(key);
    }

    /**
     * 获取指定给key，剩下的过期时间
     * @param key 要获取剩下的过期时间的键名，任意类型
     * @param unit 返回的过期时间的单位
     * @return 返回剩下的过期时间，如果没有设置过期时间，则返回-1
     */
    @Override
    public long getExpire(String key, TimeUnit unit){
        return redisTemplate.getExpire(key,unit);

    }

    /**
     * 获取指定给key，剩下的过期时间
     * @param key 要获取剩下的过期时间的键名，任意类型
     * @return 返回剩下的过期时间，单位是秒，如果没有设置过期时间，则返回-1
     */
    @Override
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    /**
     * 给指定key，设置过期时间
     * @param key 要设置过期时间的键名，任意类型
     * @param timeout 过期时间，单位是秒
     * @return 返回设置是否成功，成功返回true，如果该key原本就有过期时间，则也是返回true，并且会重新设置过期时间。如果指定的key不存在，则返回false
     */
    @Override
    public boolean expire(String key,long timeout){
        return  redisTemplate.expire(key,timeout,TimeUnit.SECONDS);
    }

    /**
     * 给指定key，设置过期时间
     * @param key 要设置过期时间的键名，任意类型
     * @param timeout 过期时间
     * @param  unit 过期时间的单位
     * @return 返回设置是否成功，成功返回true，如果该key原本就有过期时间，则也是返回true，并且会重新设置过期时间。如果指定的key不存在，则返回false
     */
    @Override
    public boolean expire(String key,long timeout,TimeUnit unit){
        return  redisTemplate.expire(key,timeout,unit);
    }

    /**
     * 删除集合里的所有key
     * @param key 要删除的键名，，任意类型
     * @return 返回成功删除key的个数
     */
    @Override
    public long batchDelKey(List<String> key){
        return redisTemplate.delete(key);
    }

    /**
     * 删除指定的key
     * @param key 要删除的键名，任意类型
     * @return 返回是否删除成功。成功为true
     */
    @Override
    public boolean delKey(String key){
        return redisTemplate.delete(key);
    }
//================================key-value============================================

    /**
     * 指定key，增长指定delta值。如果指定的key非数值，则会报错：io.lettuce.core.RedisCommandExecutionException
     * @param key 键值对的键名
     * @param delta 增长量
     * @return 返回增长后的值,如果key不存在，则会重新新建一个key
     */
    @Override
    public long incr(String key,long delta){
        return redisTemplate.opsForValue().increment(key,delta);
    }

    /**
     * 指定key，减少指定delta值。如果指定的key非数值，则会报错：io.lettuce.core.RedisCommandExecutionException
     * @param key 键值对的键名
     * @param delta 减少量
     * @return 返回减少后的值
     */
    @Override
    public long decr(String key,long delta){
        return redisTemplate.opsForValue().decrement(key,delta);
    }

    /**
     * 获取指定key的value
     * @param key 键值对的键名
     * @return 返回value
     */
    @Override
    public Object get(String key){
        if(key!=null){
            return redisTemplate.opsForValue().get(key);
        }
        return null;
    }

    /**
     * 使用lua脚本，嵌套获取一个键值对。
     * 比如：
     * 先获取keyValue的value值，该value值会作为key，再次进行get其值
     * @param keyValue 该key的value会被作为key再进行一次查询
     * @param preKey  keyValue的value作为key的前缀
     * @param targetClass 返回的class类型
     * @return 返回2次get的结果,如果第一次get为nil，则不会进行下一次get，直接返回null
     */
    @Override
    public <T> T getValueAsKey(String keyValue,String preKey,Class<T> targetClass){
        /**
         * 因为lua脚本传入的”字符串“参数是全部默认为字符串是带有""
         * 但是redis的key全都是没有"",除非你key名含有""
         * 由于我使用的是jackson，所以reids存的绝大部分是带""，除了数值类型
         */

//        StringBuffer strScript = new StringBuffer();
//        strScript.append("local keyValue = redis.call('get',KEYS[1]) ");
//        strScript.append("if not keyValue then ");
//        strScript.append("return keyValue ");
//        strScript.append("end ");
//        strScript.append("if tonumber(keyValue) then ");
//        strScript.append("local key = string.sub(ARGV[1],2,string.len(ARGV[1])-1)..tostring(keyValue) ");
//        strScript.append("return redis.call('get',key) ");
//        strScript.append("else ");
//        strScript.append("return redis.call('get',string.sub(keyValue,2,string.len(keyValue)-1)) ");
//        strScript.append("end");

        DefaultRedisScript<T> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("resources\\lua\\getValueAsKey.lua")));
        script.setResultType(targetClass);
        return (T)execLua(script, Arrays.asList(keyValue),preKey);
    }

    /**
     * 添加一个k-v，不管key存不存在，如果存在，则直接覆盖原本的k-v
     * @param key 键值对的键名
     * @param value 值
     */
    @Override
    public void set(String key,Object value){
        redisTemplate.opsForValue().set(key,value);
        return;
    }

    /**
     * 设置一个k-v。如果key不存在，则添加k-v，否则不添加
     * @param key 键值对的键名
     * @param value 值
     * @return 如果添加了返回true，否则返回false
     */
    @Override
    public boolean setNx(String key,Object value){
        return redisTemplate.opsForValue().setIfAbsent(key,value);
    }

    /**
     * 设置一个k-v。如果key不存在，则添加k-v,并为该k-v添加过期时间，单位是秒
     * @param key 键值对的键名
     * @param value 值
     * @param timeout 过期时间，单位是秒
     */
    @Override
    public void setEx(String key,Object value,long timeout){
        setEx(key,value,timeout,TimeUnit.SECONDS);
    }

    /**
     * 设置一个k-v。如果key不存在，则添加k-v,并为该k-v添加过期时间
     * @param key 键值对的键名
     * @param value 值
     * @param timeout 过期时间
     * @param unit 过期时间的单位
     */
    @Override
    public void setEx(String key,Object value,long timeout, TimeUnit unit){
        redisTemplate.opsForValue().set(key,value,timeout,unit);
    }

//===============================hashmap=============================================
    /**
     * 判断指定key集合map里的field字段是否存在
     * @param key map集合的键名
     * @param field 字段
     * @return 返回存在性，如果存在，返回true。如果map集合不存在，则也是返回false
     */
    @Override
    public boolean hExistField(String key, String field){
        return redisTemplate.opsForHash().hasKey(key,field);
    }

    /**
     * 获取指定key集合map里的指定字段对应的value
     * @param key map集合的键名
     * @param field 字段
     * @return 返回指定key集合map里的指定字段对应的value
     */
    @Override
    public Object hGet(String key,String field){
        return redisTemplate.opsForHash().get(key,field);
    }

    /**
     * 获取指定key集合map
     * @param key map集合的键名
     * @return 返回指定key集合map，如果不存在，则返回null
     */
    @Override
    public Map<Object, Object> hGetMap(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 如果指定key集合map不存在，则创建一个key集合map，并将map集合存入其中
     * @param key map集合的键名
     * @param map map集合
     */
    @Override
    public <T> void hSetMap(String key,Map<String,T> map){
        redisTemplate.opsForHash().putAll(key,map);
    }

    /**
     * 如果指定key集合map不存在，则会创建一个集合map。并将field存入其中
     * 如果key集合里的field存在，则不会将field-value存入其中
     * @param key map集合的键名
     * @param field 字段(map集合中的key)
     * @param value 值(map集合里的value)
     * @return 如果field-value成功存入map中，则返回true
     */
    @Override
    public boolean hPutNx(String key,String field,Object value){
        return redisTemplate.opsForHash().putIfAbsent(key,field,value);
    }

    /**
     * 如果指定key集合map不存在，则创建一个key集合map，并将field-value存入其中
     * @param key map集合的键名
     * @param field 字段(map集合中的key)
     * @param value 值(map集合里的value)
     */
    @Override
    public void hPut(String key,String field,Object value){
        redisTemplate.opsForHash().put(key,field,value);
    }

    /**
     * 删除指定key集合map里的field字段所对应的k-v
     * @param key map集合的键名
     * @param fields 字段(map集合中的key)
     * @return  返回删除后，map集合里剩下的k-v的个数
     */
    @Override
    public long hDel(String key,String... fields){
        return redisTemplate.opsForHash().delete(key,fields);
    }
//===============================set集合=============================================

    /**
     * 判断指定key集合set里的value是否存在
     * @param key set集合的键名
     * @param value 要知道存在的值
     * @return 返回存在性，true存在，false不存在，如果指定key不存在，也是返回false
     */
    @Override
    public boolean sExist(String key, Object value){
        return redisTemplate.opsForSet().isMember(key,value);
    }

    /**
     * 随机获取指定key集合set里的指定元素个数。注意：是可以重复获取的
     * @param key set集合的键名
     * @param count 获取元素的个数
     * @return 返回随机获取到的元素，如果key不存在，则返回空
     */
    @Override
    public List<Object> sRandomGet(String key,long count){
        return redisTemplate.opsForSet().randomMembers(key,count);
    }

    /**
     * 获取指定key集合的set
     * @param key set集合的键名
     * @return 返回set集合，如果key集合set不存在，则返回空
     */
    @Override
    public Set<Object> sGetSet(String key){
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 如果指定key集合Set不存在，则创建一个set集合，并将values存入其中
     * @param key set集合的键名
     * @param values 值
     * @return 返回添加values后set集合里的元素个数
     */
    @Override
    public long sAdd(String key,Object... values){
        return redisTemplate.opsForSet().add(key,values);
    }

    /**
     * 移除指定key集合Set里的values
     * @param key set集合的键名
     * @param values 要移除的值
     * @return 返回移除后set集合里的元素个数。如果key不存在，返回0
     */
    @Override
    public long sRemove(String key,Object... values){
        return redisTemplate.opsForSet().remove(key,values);
    }
//================================列表===============================================

    /**
     * 获取指定key列表的长度
     * @param key 列表的键名
     * @return 返回列表长度，如果key不存在，返回0
     */
    @Override
    public long lGetListSize(String key){
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 获取指定key列表的指定范围内的子列表
     * @param key 列表的键名
     * @param start 子列表的起始元素下标，如果为负数，则认为是0
     * @param end  子列表的终止元素下标
     * @return 返回子列表，如果指定的end或者start超过列表下标，则返回不超过的部分,如果全部超出或者key不存在，则返回空
     */
    @Override
    public List<Object> lGet(String key,long start,long end){
        return  redisTemplate.opsForList().range(key,start,end);
    }

    /**
     * 获取指定key列表的指定下标元素
     * @param key 列表的键名
     * @param index 下标
     * @return 返回指定下标元素,如果超出范围，或者key不存在，则返回null
     */
    @Override
    public Object lGetIndex(String key,long index){
        return redisTemplate.opsForList().index(key,index);
    }

    /**
     * 如果key存在，则右追加，如果不存在，则不创建列表，也不追加value
     * @param key 列表的键名
     * @param value 值
     * @return 返回列表中的元素个数。如果key列表不存在，返回0
     */
    @Override
    public long lRPushNx(String key,Object value){
        return redisTemplate.opsForList().rightPushIfPresent(key,value);
    }

    /**
     * 如果key存在，则左追加，如果不存在，则不创建列表，也不追加value
     * @param key 列表的键名
     * @param value 值
     * @return 返回列表中的元素个数。如果key列表不存在，返回0
     */
    @Override
    public long lLPushNx(String key,Object value){
        return redisTemplate.opsForList().leftPushIfPresent(key,value);
    }

    /**
     * 如果key不存在，则会创建一个key列表，将values从左到右依次右放入指定key列表中
     * @param key 列表的键名
     * @param values 值
     * @return 返回列表中的元素个数
     */
    @Override
    public long lRPush(String key,Object... values){
        return redisTemplate.opsForList().rightPushAll(key,values);
    }

    /**
     * 如果key不存在，则会创建一个key列表，将values从左到右依次左放入指定key列表中
     * @param key 列表的键名
     * @param values 值
     * @return 返回列表中的元素个数
     */
    @Override
    public long lLPush(String key,Object... values){
        return redisTemplate.opsForList().leftPushAll(key,values);
    }

    /**
     * 指定key列表左弹出第一个元素。即第一个元素将出队
     * @param key 列表的键名
     * @return 返回弹出的元素，如果key不存在返回null
     */
    @Override
    public Object lLPop(String key){
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 指定key列表右弹出第一个元素。即第一个元素将出队
     * @param key 列表的键名
     * @return 返回弹出的元素，如果key不存在返回null
     */
    @Override
    public Object lRPop(String key){
        return  redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 除去指定key列表里指定范围内的元素。如果超出范围，则只去除不超出范围的元素
     * @param key 列表的键名
     * @param start 起始元素下标
     * @param end 终止元素下标（包含）
     */
    @Override
    public void lTrim(String key,long start,long end){
        redisTemplate.opsForList().trim(key,start,end);
    }
//=============================有序集合===========================================

    /**
     * 查找有序集合中value的排名，即按分数升序排名，排名从0开始，即0对应的则是最小分数
     * @param key 有序集合的键名
     * @param value 要知道其排名的值
     * @return 按分数升序排名，返回value的在有序集合中的排名。如果value不在有序集合中，则返回null
     */
    @Override
    public Long zRank(String key,Object value){
        return redisTemplate.opsForZSet().rank(key,value);
    }

    /**
     * 注意：该方法是升序，即从小到大排序
     *筛选出key有序集合中分数介于[min,max]中的所有元素，并从筛选出来的结果中，对头偏移offset个元素，
     * 返回偏移后的count个元素。
     * 比如：min：1 max：5 offset：2 count：1
     * 则返回分数介于【1,5】的元素,假设有5个(1,2,3,4,5),那么偏移2个，则得到(3,4,5)返回1个元素，即 3。
     * @param key 有序集合的键名
     * @param min 最小分数
     * @param max 最大分数
     * @param offset 偏移量
     * @param count 返回的元素个数
     * @return 筛选后的元素集合
     */
    @Override
    public <T> Set<T> zsRangeByScoreAsc(String key, double min, double max, long offset, long count){
        return (Set<T>) redisTemplate.opsForZSet().rangeByScore(key,min,max,offset,count);
    }

    /**
     * 注意：该方法是降序，即从大到小排序
     *筛选出key有序集合中分数介于[min,max]中的所有元素，并从筛选出来的结果中，对头偏移offset个元素，
     * 返回偏移后的count个元素。
     * @param key 有序集合的键名
     * @param min 最小分数
     * @param max 最大分数
     * @param offset 偏移量
     * @param count 返回的元素个数
     * @return 筛选后的元素集合
     */
    @Override
    public <T> Set<T> zsRangeByScoreDesc(String key, double min, double max, long offset, long count){
        return (Set<T>) redisTemplate.opsForZSet().reverseRangeByScore(key,min,max,offset,count);
    }

    /**
     * 获取指定元素下标范围里的所有元素
     * @param key 有序集合的键名
     * @param start 元素的起始下标
     * @param end 元素的结尾下标（包含）
     * @return 返回范围内的元素集合，如果超出范围，返回空set，而不是null
     */
    @Override
    public <T> Set<T> zsRange(String key, long start, long end){
        return  (Set<T>)redisTemplate.opsForZSet().range(key,start,end);
    }

    /**
     * 如果有序集合key不存在，则创建一个有序集合key，并添加一个分数为score的value到key有序集合中
     * 如果插入的value已经存在，则会更新它的分数score。返回false
     * 如果插入的分数已经有相同的，只能说明同等级
     * @param key 有序集合的键名
     * @param value 值
     * @param score 分数，用于在有序集合中排名
     * @return 添加的value不存在，则返回true，如果存在，则返回false
     */
    @Override
    public boolean zsAdd(String key,Object value,double score){
        return redisTemplate.opsForZSet().add(key,value,score);
    }

    /**
     * 如果有序集合key不存在，则创建一个有序集合key，并批量添加元素到有序集合中
     * @param key 有序集合的键名
     * @param values 要添加的值(值，分数)
     * @return 返回成功添加的个数，即添加的value不存在，则返回true，如果存在，则返回false
     */
    @Override
    public long zsAdd(String key,Set<ZSetOperations.TypedTuple<Object>> values){
        return redisTemplate.opsForZSet().add(key,values);
    }

    /**
     * 移除指定key有序集合里的所有values
     * @param key 有序集合的键名
     * @param values 值
     * @return 返回成功删除元素的个数
     */
    @Override
    public long zsRemove(String key,Object... values){
        return redisTemplate.opsForZSet().remove(key,values);
    }

    /**
     * 移除指定分数范围内的元素
     * @param key 有序集合的键名
     * @param min 最小分数
     * @param max 最大分数
     * @return 返回移除元素的个数
     */
    @Override
    public  long zsRemoveRangeByScore(String key,double min,double max){
        return  redisTemplate.opsForZSet().removeRangeByScore(key,min,max);
    }
    //=============================HyperLogLog基数===========================================

    //=============================geospatial地理位置===========================================
}
